package org.third.security.crypto;

import com.fasterxml.jackson.core.JsonProcessingException;

import org.junit.Test;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.util.Set;

public enum CryptoAlgorithm {
    /**
     * <pre>
     *  如基本的单向加密算法： 
                BASE64 严格地说，属于编码格式，而非加密算法
                MD5(Message Digest algorithm 5，信息摘要算法)
                SHA(Secure Hash Algorithm，安全散列算法)
                HMAC(Hash Message Authentication Code，散列消息鉴别码)
                
                复杂的对称加密（DES、PBE）： 
                DES(Data Encryption Standard，数据加密算法)
                PBE(Password-based encryption，基于密码验证)
                AES(Advanced Encryption Standard)
                非对称加密算法： 
                RSA(算法的名字以发明者的名字命名：Ron Rivest, AdiShamir 和Leonard Adleman)
                DH(Diffie-Hellman算法，密钥一致协议)
                DSA(Digital Signature Algorithm，数字签名)
                ECC(Elliptic Curves Cryptography，椭圆曲线密码编码学)
    
    
     * 使用AES加密时，当密钥大于128时，代码会抛出java.security.InvalidKeyException: Illegal key size or default parameters
    Illegal key size or default parameters是指密钥长度是受限制的，java运行时环境读到的是受限的policy文件。文件位于${java_home}/jre/lib/security
                这种限制是因为美国对软件出口的控制。
                
                解决办法：
                去掉这种限制需要下载Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files.网址如下。
                下载包的readme.txt 有安装说明。就是替换${java_home}/jre/lib/security/ 下面的local_policy.jar和US_export_policy.jar
    jdk5: http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-java-plat-419418.html#jce_policy-1.5.0-oth-JPR
    jdk6: http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
    jdk7: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
    jdk8: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
     * </pre>
     */
    MD5,     //
    SHA,     //
    /**
     * HMAC( Hash Message Authentication Code) 散列消息鉴别码，基于密钥的Hash算法的认证协议。消息鉴别码实现鉴别的原理是，用公开函数和密钥产生一个固定长度的值作为认证标识，
     * 用这个标识鉴别消息的完整性。使用一个密钥生成一个固定大小的小数据块，即MAC，并将其加入到消息中，然后传输。 接收方利用与发送方共享的密钥进行鉴别认证等。
     */
    HmacMD5(),     //

    ///////// 
    AES("AES", 128),     // 128,192,256
    PBEWithMD5AndDES("PBEWithMD5AndDES", 23),     //

    // Asymmetric crypto; signature
    RSA("RSA", 1024),     // RSA (1024, 2048), matched Signature: RSA, MD5WITHRSA,
    DSA("DSA", 1024),     // DSA (1024)
    DiffieHellman("DiffieHellman", 1024),     // DiffieHellman (1024)

    /////////// Signature
    MD5withRSA("MD5withRSA", 1024),
    //
    ;

    private String name;
    private int keySize;

    private CryptoAlgorithm() {
        this.name = name();
        this.keySize = 0;
    }

    private CryptoAlgorithm(String name, int keySize) {
        this.name = name;
        this.keySize = keySize;
    }

    public String getName() {
        return name;
    }

    public int getKeySize() {
        return keySize;
    }

    /**
     * 将二进制转换成16进制
     * 
     * @param buf
     * @return
     */
    public static String parseByteToHexStr(byte buf[]) {
        // return org.jasypt.commons.CommonUtils.toHexadecimal(buf);
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    /**
     * 将16进制转换为二进制
     * 
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStrToByte(String hexStr) {

        // return org.jasypt.commons.CommonUtils.fromHexadecimal(hexStr);
        if (hexStr.length() < 1)
            return null;
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    public static final byte[] base64Decode(String hexStr) throws UnsupportedEncodingException {
        return java.util.Base64.getDecoder().decode(hexStr.getBytes("UTF-8"));

    }

    public static final String base64Encode(byte[] buff) throws UnsupportedEncodingException {
        return new String(java.util.Base64.getEncoder().encode(buff), "UTF-8");
    }

    @Test
    public static void printAllSupportedEncryption() throws JsonProcessingException {
        java.security.Provider[] providers = Security.getProviders();
        for (int p = 0, pSize = providers.length; p < pSize; ++p) {
            java.security.Provider provider = providers[p];
            System.out.println(("idx_" + p +":"+provider.getName()+ ": ") + provider.getClass());
            Set<java.security.Provider.Service> services = provider.getServices();

            for (java.security.Provider.Service service : services) {
                                System.out.println("  " + service.getType() + ":" + service.getAlgorithm() + ":" + service.getClass());
            }
        }
    }

    public static void main(String[] args) throws Exception {
        printAllSupportedEncryption();
        
//        System.out.println(Security.getAlgorithms("Cipher"));
        
        System.setProperty("java.security.debug", "all");
         
        String initData = "332343adfasdf234";
        byte[] salt = initData.getBytes();
        PBEKeySpec pbeKeySpec;
        try {
            pbeKeySpec = new PBEKeySpec(initData.toCharArray(), initData.getBytes(), 12);
        } catch (Exception e) {
            throw new LWCryptoException("Unable to create PBEKeySpec object.", e);
        }

        String pbeCryptoAlgorithmName = "PBEWithMD5ANdTripleDES";
        SecretKeyFactory keyFact;
        try {
            keyFact = SecretKeyFactory.getInstance(pbeCryptoAlgorithmName);
        } catch (Exception e) {
            throw new LWCryptoException("Unable to create an instance of SecretKeyFactory for cipher: " + AES, e);
        }

        Key key;
        try {
            key = keyFact.generateSecret(pbeKeySpec);
        } catch (InvalidKeySpecException e) {
            throw new LWCryptoException("Unable to create secret key from given spec", e);
        }

        Cipher cipher = Cipher.getInstance(pbeCryptoAlgorithmName);
        salt = new byte[8];
        System.arraycopy(initData.getBytes(), 0, salt, 0, salt.length);
        cipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(salt, 20));

    }

}
